Skip to content

Fix keyed iterator value lifetime across reentrant key() calls#22690

Open
PuH4ck3rX wants to merge 1 commit into
php:PHP-8.4from
PuH4ck3rX:agent/fix-keyed-iterator-reentry-uaf
Open

Fix keyed iterator value lifetime across reentrant key() calls#22690
PuH4ck3rX wants to merge 1 commit into
php:PHP-8.4from
PuH4ck3rX:agent/fix-keyed-iterator-reentry-uaf

Conversation

@PuH4ck3rX

Copy link
Copy Markdown

Keyed iterator consumers fetch the current value before fetching the key. Fetching the key may call user code, and a reentrant key() implementation can advance a RecursiveIteratorIterator far enough to destroy the child zend_user_iterator that owns the cached current-value zval. iterator_to_array() and keyed foreach then continue using the borrowed pointer.

This change copies the current zval before invoking get_current_key() in both consumers:

  • spl_iterator_to_array_apply() uses the retained value for insertion and releases it on success or exception.
  • zend_fe_fetch_object_helper retains the value across key lookup and releases it after CV or temporary-result assignment.

The generated VM executor is updated accordingly.

The regression test deterministically replaces the destroyed child iterator with a same-layout IteratorIterator. Before the fix, iterator_to_array() and both foreach assignment forms consume the replacement value; after the fix, they preserve the value fetched before key() reentry.

Checks performed:

  • Built PHP 8.4 with ASan and UBSan, with opcache JIT disabled.
  • The new regression test passed.
  • 7 reduced reproducer scripts completed without sanitizer findings.
  • 47 targeted foreach and iterator tests passed.
  • The complete SPL test suite passed on the native WSL filesystem: 764 passed, 10 skipped, 1 expected failure, 0 failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant